/*******************************************************************\
*	file: 		atalkXCMD.c											*
*	version:	1.06§ 												*
* 																	*
*																	*
* The following XCMD's give you AppleTalk capability (at the the 	*
* transaction and name-binding protocol layers) to Hypercard. While	*
* not an exhaustive treatment, it's a start.						*
*																	*
* code known to work with correctly with the prototype as a client	*
* and/or server														*
* -----------------------------------------------------------------	*
*  Routine	    |		 Description								*
* -----------------------------------------------------------------	*
*																	*
*			--- AppleTalk Transaction Protocol (ATP) Routines ---	*
* ATPInit		Check to see if port available and appletalk drivers*
*				loaded, allocate required buffers.  Open a server	*
*				socket for this node.								*
* ATPKill		Disassociate ourselves with the	net deallocate		*
*				buffers												*
* PollRequests	Poll the Idle queue and return info					*
* Request		Determine whether a request can be sent or needs to	*
*				be added to the waiting queue.						*
*				entity.												*
*																	*
*			--- Name Binding Protocol (NBP) Routines ---			*
* NBPInit		set up the NBP stuff 								*
* NBPKill		close down the NBP stuff							*
* ConfirmName	confirm that an entity is still visible (should be	*
*				done before sending a message to that entity)		*
* ExtractName	Extract a name from the Lookup buffer				*
* Lookup		Look up other entities on the net and put the 		*
*				results in LookUpBuffer.							*
* NodeRegister	Get the name of this node and register it in the	*
*				socket listening table.								*
*																	*
*			--- Miscellaneous Support Routines ---					*
* DeQueueRequest													*
*				Remove an element from waiting queue (FIFO style)	*
* QueueGetRequest													*
*				Queue a GetRequest on the server side to "listen" 	*
*				for incoming client requests.						*
* QueueRequest	Queue a request that cannot be services due to lack	*
*				of sockets.  Copy all the data into queue structure	*
* Respond		Respond to a request with the given message			*
* SendRequest	Set up an ATP parameter block and send the request	*
* SendWaitingRequest 												*
*				Attempt to send a queued request.  If successful, 	*
*				dequeue it, otherwise leave it in the queue for		*
*				another try later.									*
* -----------------------------------------------------------------	*
* By:	Donald Koscheka												*
*		with Gratitudes to Kerry Lynn for debugging help			*
*		and Platitudes to Greg Kimberly for thinking up the idea	*
* Date:	21-Sept-87													*
* ©	Copyright 1987, Apple Computer, Inc.							*
*	All Rights Reserved												*
*																	*
* -----------------------------------------------------------------	*
*						Modification History						*
* -----------------------------------------------------------------	*
*  Date	   	| By	|	 				Description					*
* -----------------------------------------------------------------	*
* 9/21/87	| DK	| file created									*
* 10/1/87	| DK	| modified to work with preferred ATP calls		*
* 10/5/87	| DK	| relocate the master block at heap high		*
* 10/6/87	| DK	| added dynamically allocated lookup buffer		*
* 10/7/87	| DK 	| separated response from Poll Request			*	
* 10/7/87	| DK	| extract responses during Poll_Requests		*
* 11/5/87	| DK	| modified all routines to return the error		*
*			| DK	| removed paramBlock from all calls not making	*
*			|		| callbacks to hypercard.						*
*			|		| modified code to reflect changes in 11/5 spec	*
* -----------------------------------------------------------------	*
*					Alpha Release 0.5	11/30/87					*
* -----------------------------------------------------------------	*
* 12/1/87	| DK	| Modified receive to put received data in		*
*			|	 	| global pool and call back with method only	*
* 12/2/87	| DK	| Modified waiting queue to act as a FIFO		* 
* 			| DK	| issue responses asynchronously.				*
* 12/3/87	| DK	| Added full response packet transmission		*
* 12/7/87	| DK	| Converted NBP calls to preferred (paramblock)	*
* 12/10/87	| DK	| General bug fixes - check handles...			*
* 12/11/87	| DK	| converted nonrels from locked handles to Ptrs	*
* -----------------------------------------------------------------	*
*					Beta Release 1.05§	12/15/87					*
* -----------------------------------------------------------------	*
*  1/14/88	| DK	| Converted all routines to access names table	*
*			|		| Uncouple NBP and ATP stuff					*
*  1/26/88	| DK	| set SPConfigP to 0x01 to indicate Appletalk is*
*			|		| is using port B								*
*  2/10/88	| DK	| Set atp.checkpoint flag to signal that we're 	*
*			|		| currently in ATPReceive (ATPClose can't close)*
*			|		| or ATPClose sets to tell ATPReceive to close	*
* 22-Feb-88 | DK	| Move all locked handles high					*
* 29-Feb-88	| DK	| Dispose handle created for response callback	*
* -----------------------------------------------------------------	*
\*******************************************************************/

/*******************************************************************\
* HUH???? Experienced "C" programmers will note the capricious		*
* use of the commented "auto" storage class.  I do this so that 	*
* my data declarations "line up" as: CLASS TYPE NAME = INITIALIZER	*
* This pedantry is provided as an aid to the beginner.  Also, 		*
* remembering that automatics are stack-bound might help the novice	*
* look for them in a stack frame from the debugger(Hint: A6 is the	*
* frame pointer).   												*
*														-DK			*
\*******************************************************************/


#include	<Types.h>
#include 	<AppleTalk.h>
#include	<Resources.h>
#include 	<OSUtils.h>
#include	<Memory.h>
#include	<Strings.h>
#include	"atalkxcmd.h"
#include	"xcmdutils.h"
#include 	"HyperXCmd.h"


/*******************************************************************\
*	AppleTalk Transaction Protocol (ATP) Interface Routines			*
*																	*
\*******************************************************************/

ATPBlock	*ATPInit( entcount, type )
	short			entcount;
	short			type;
/*********************************
* Check to see if PortB is in use by
* a serial driver or wheter it is 
* available to us for appletalk.  If
* it is available, check PortBUse to
* determine whether appletalk is in.
*
* Allocate our buffers and open a 
* listening socket for this node 
* (talkers are allocated dynamically).
* queue up enough requests to get
* us going...
*
* In: 		short = number of entitities
*			to allocate
*
* Out:		Pointer to the allocated ATPBlock
*			nil of the session was already initialized
*
* NOTE: We attempt to allocate (3) 
*		nonrels - ATPBlock, ServerBlock
*		and ClientBlock.  These objects
*		MUST NOT move during the session
*		(async I/O is depending on it)
*********************************/
{
	/* auto */		char			*PortBUseP;
	/* auto */		char			*SPConfigP;
	/* auto */		char			use, con;
	/* auto */		char			*temp;
	/* auto */		OSErr			error;	
	/* auto */		short			i;
	/* auto */		ATPParamBlock	pb;
	register		ATPBlock		*mbPtr;
	
	/* point to and get the needed lowmem stuff.  */
	PortBUseP = (char *)PortBUse;
	SPConfigP = (char *)SPConfig;
	use = *PortBUseP;
	con = *SPConfigP;
	
	if((con & 0x0F) > useATalk) /** if the port is in use we can't usurp it **/ 
		return( nil );
	
	if( (use & 0xFF || use & 0x0F) == useATalk){
		if(!IsMPPOpen())
			if((error = MPPOpen()) != noErr)
				return( nil );
	
		if( !IsATPOpen() ){
			if (error = ATPLoad() != noErr)
				return( nil );
		
			*PortBUseP |= 0x04;
			*PortBUseP &= 0x7F;
		}
	}
	else
		return( nil );

	*SPConfigP |= 0x01;	/*** set for appletalk 	***/

	/*** attempt to allocate the data 				***/
	if( !(mbPtr	= (Ptr)NewPtr( sizeof( ATPBlock ))) )
		return( nil );

	/*** 			------	Initialize the Master Block ------			***/
	mbPtr->Server			= nil;
	mbPtr->Client			= nil;
	mbPtr->FirstRequest 	= nil;		/***  no requests queued 		***/
	mbPtr->LastRequest 		= nil;
	mbPtr->checkPoint		= CLOSE_OK;	/**** 2/10/88					***/
	
	temp = mbPtr->autoRsp;
	*temp++ = 'O';					/*** if you receive this message 	***/
	*temp++ = 'K';					/*** from a callback, you know the 	***/
	*temp++ = 'A';					/*** transaction succeeded.  		***/
	*temp++ = 'Y';					/*** Isn't that special?			***/
	*temp  = '\0';
					
	/*** 			------	Initialize the server side	------			***/
	mbPtr->ServerAddr.aSocket = 0;
	mbPtr->ServerAddr.aNode = 0;
	mbPtr->ServerAddr.aNet = 0;

	if( type && SERVER ){
		pb.ATPaddrBlock.aNet 	= (short)0;
		pb.ATPaddrBlock.aNode	= 0;
		pb.ATPaddrBlock.aSocket	= 0;
		pb.ATPatpSocket			= 0;

		if( !POpenATPSkt( &pb , SYNC ) )	/*** noErr means we have a socket***/
			mbPtr->ServerAddr.aSocket	= (unsigned char)pb.ATPatpSocket;
		else{
			ShutMeDown( mbPtr );
			return( nil );
		}
		
		/*** initialize getrequests array ***/
		{	
			register	ServerPtr	sp = mbPtr->Server;
			
			mbPtr->Server = (ServerPtr)NewPtr( sizeof( ServerBlock )*SQSIZE );
			if( mbPtr->Server )
				sp = mbPtr->Server;
			else{
				ShutMeDown( mbPtr );
				return( nil );
			}	
			for( i=0; i < SQSIZE; i++ )
				QueueGetRequest( &(sp[i]), &(mbPtr->ServerAddr) );
		}
	}
	
	/*** 			------	Initialize the client side	------				***/
	if(  type && CLIENT ){					
		/*** initialize the send request queue ***/
		{	
			ClientPtr	cp;
		
			mbPtr->Client = (ClientPtr)NewPtr( sizeof( ClientBlock ) * CQSIZE );
			if( mbPtr->Client )
				cp = mbPtr->Client;
			else{
				ShutMeDown( mbPtr );
				return( nil );
			}
			for( i=0; i < CQSIZE; i++ ){
				cp[i].isInUse	= IDLE;
				cp[i].rspM		= nil;
			}
		}
	}
	return( mbPtr );
}


ShutMeDown( atp )
	ATPBlock		*atp;
/****************************
* Given a handle to an allocated
* ATPBlock, deallocate all its
* substructures and itself
*
* called only after a memory failure
* and atp already allocated.
****************************/
{
	if( atp->Server )
		DisposPtr( atp->Server );
	
	if( atp->Client )
		DisposPtr( atp->Client );

	DisposPtr( atp );
}


short	ATPKill(  atp )
	ATPBlock		*atp;
/*********************************
* Remove ourselves from the entity
* table, dislocate our socket and 
* deallocate all memory whose handles
* were provided...
*
* In: 		Handle to ATPBlock
*
* Out:		noErr if successful
*			OSErr otherwise
*********************************/
{
	/* auto */	OSErr			error = noErr;
	/* auto	*/	OSErr			err;
	/* auto */	short			i;
	/* auto */	ATPParamBlock	pb;

	/*** 	Close Down and Deallocate the SERVER side 	***/
	if( atp->Server ){
		register	ServerPtr	sp = atp->Server;
		
		if( atp->ServerAddr.aSocket ){
			/*** close down any outstanding get requests***/

			for( i = 0; i < SQSIZE; i++ )
				if( sp[i].isInUse && sp[i].pb.ATPioResult > 0 ){
					pb.ATPaKillQEl = &(sp[i].pb);
					err = PKillGetReq( &pb, SYNC );
					if( err )
						error = err;
				}
			
			/*** shut down the server socket 			***/
			pb.ATPatpSocket = atp->ServerAddr.aSocket;
			err = PCloseATPSkt( &pb, SYNC );
			if( err )
				error = err;
		}
	}
	
	/*** 	Close Down and Deallocate CLIENT side 		***/
	if( atp->Client ){		
		register	ClientPtr	cp = atp->Client;
		
		/*** Kill outstanding SendRequests 				***/
		for( i = 0; i < CQSIZE; i++ )
			if( cp[i].isInUse && cp[i].pb.ATPioResult > 0 ){
				pb.ATPaKillQEl = &(cp[i].pb);
				err = PKillSendReq( &pb, SYNC );
				if( err )
					error = err;

				if( cp[i].rspM ){
					HUnlock( cp[i].rspM );
					DisposHandle( cp[i].rspM );
				}
			}
		
		/*** deallocate all waiting queue elements 		***/
		if( atp->FirstRequest ){
			/* auto */	ClientQH	nq, cq;
			register	ClientQPtr	pq;
		
			cq = atp->FirstRequest;
			
			while( cq ){
				nq = (**cq).next;
				pq = *cq;
				
				if( pq->rsp ){
					HUnlock( pq->rsp );
					DisposHandle( pq->rsp );
				}
				
				if( pq->buf ){
					HUnlock( pq->buf );
					DisposHandle( pq->buf );
				}
				HUnlock( cq );
				DisposHandle( cq );
				cq = nq;
			}
		}
	}
	ShutMeDown( atp );
	return( error );
}


short	PollRequests( paramPtr, atp, rspMess )
	XCmdBlockPtr	paramPtr;
	ATPBlock		*atp;
	Handle			rspMess;
/*********************************
* PollRequests
*
* Performs 3 functions:
*
* (1) Handle Received Requests on Server Side
*
* (2) Handle Succesful termination of Responses
*
* (3) Handle Received Responses on Client Side
*
*********************************/
{
	/* auto */	OSErr	error = noErr;
	/* auto */	short	i;

	if( atp->Server  ){
		error = Get_Requests_From_Clients( paramPtr, atp, rspMess );
		error = Check_Response_Sent( paramPtr, atp );
	}
	
	if( atp->Client )
		error = Get_Responses_From_Servers( paramPtr, atp );
		
	if( error > 0 ) 
		error = 0;
	return(  error );
}


short	Request(  atp, theData, theSize, addr, rmess, cnt, intv )
	ATPBlock		*atp;
	unsigned char	*theData;
	short			theSize;
	AddrBlock		*addr;
	Handle			rmess;
	short			cnt, intv;
/*********************************
* If a transaction block is available,
* send the request on to ATP.  Otherwise,
* add it to the waiting queue.
*
* In:		theData		== the data to use
*			theSize		== the size of the data
*			rmess		== handle to the message to 
*						   append the response to
*			addr		== tuple of the receiver
*			retry, timeout
*
* Out:		noErr if successful
*			OSErr otherwise
*********************************/
{
	/* auto */	OSErr		error = noErr;
	/* auto */	short		i;
	register	ClientPtr	cp	= atp->Client;
	
	if( !atp->Client )					/*** take a powder you chowder head 	***/
		return( DEFAULT_ERROR );
		
	/*** find the next available client transaction block (if any)				***/
	for( i = 0; i < CQSIZE; i++ )
		if(  cp[i].isInUse == IDLE ){
			error = SendRequest( &(cp[i]),theData, theSize, rmess, addr, cnt, intv );
			return( error );
		}
		
	/*** didn't have an available transaction block, or the request failed to	***/
	/*** to get satisfied (probably no available sockets), so pend the request	***/
	QueueRequest( atp, theData, theSize, rmess, addr, cnt, intv );

	return( noErr );		/*** queued requests don't fail						***/
}


/*******************************************************************\
*	 			PollRequests Server/Client Routines					*
\*******************************************************************/


Get_Requests_From_Clients( paramPtr, atp, rspMess )
	XCmdBlockPtr	paramPtr;
	ATPBlock		*atp;
	char			**rspMess;
/*********************************
* Get_Requests_From_Clients
*
* For all elements in the getRequest queue:
* if a getRequest is completed:
*	- get the request data and put it into the global receive container
*	- call hypercard back with the getrequest message
*	- the responding method will put the response in "GlobalRSPData"
*	- if the response message is nil, do an auto respond
*	  else send the response in the global container "GlobalRSPData"
*	- deallocate the request and requeue a fresh one
*
* In: 		paramPtr	= used for callbacks
*			atp		= ATPBlock pointer
*			rspMess	= message to send back to 
*						  hypercard with Server-side getrequests.
*
* Out:		calls back hypercard with the request
*			error result returned.
*********************************/
{	
	/* auto */	short		i;
	/* auto */	OSErr		error = noErr;
	/* auto */	long		rs;
	/* auto */	Handle		name, respData;
	/* auto */	long		respSize;
	register	ServerPtr	sp;

	if( rspMess )
		c2pstr( *rspMess );
			
	sp = atp->Server;
	for( i = 0;  i < SQSIZE ;  i++ ){
		error = sp[i].pb.ATPioResult;					/*** didn't take***/
		if( sp[i].isInUse == WAIT_REQUEST ){
			if( error < 0 )
				QueueGetRequest( &(sp[i]), &(atp->ServerAddr) );/*** try again	***/
			else
				if( !error ){
					/*** put the request in a Hypercard global				***/
					name = GetResource('STR ', GlobalRcvData ); 	
					respSize = (long)sp[i].pb.ATPreqLength;
					respData = NewHandle( respSize );
					BlockMove( sp[i].pb.ATPreqPointer, *respData, respSize );
					MoveHHi( name );
					HLock(name);	
					SetGlobal( paramPtr, *name, respData ); 
					HUnlock(name);
					DisposHandle( respData ); /* 2/29/88 */
					
					/*** now call the responding method (if any)			***/
					if(  rspMess ){
						MoveHHi( name );
						HLock( rspMess );
						SendCardMessage( paramPtr, *rspMess );
						HUnlock( rspMess );
					}
					
					/*** if the responding method has a response to send	***/
					/*** then send it, otherwise "autorespond" using the	***/
					/*** canned response "OKAY"								***/
					name = GetResource('STR ',GlobalRspData ); 	
					MoveHHi( name );
					HLock(name);											
					respData = GetGlobal(paramPtr,*name); 
					HUnlock(name);	
				
					if ( respData && **respData ){
						rs = GetHandleSize( respData );
						MoveHHi( respData );
						HLock( respData );
						Respond(  &(sp[i]), &(atp->ServerAddr), *respData, (short)rs );
						HUnlock( respData );
					}
					else						/***  send the autoresponse 	***/
						Respond( &(sp[i]),&(atp->ServerAddr),atp->autoRsp,(short)AUTOSIZE);

					if( respData )
						DisposHandle( respData ); /* 2/29/88 */
			}
		}
	}/*** for loop ***/		
}


Check_Response_Sent(paramPtr, atp )
	XCmdBlockPtr	paramPtr;
	ATPBlock		*atp;
/*********************************
* Send_Responses_To_Clients
*
* A server transaction block is considered
* in use from the time that the get request
* is queued unitl the TREL is received from 
* the client (acknowledging receipt of the 
* response).
*
* Actually, all this routine does is wait
* for the sent response to complete and
* requeue a get request when it does.
*
* In: 		paramPtr	= used for callbacks
*			atp		= ATPBlock pointer
*
*********************************/
{
	/* auto */	short		i;
	/* auto */	OSErr		error = noErr;
	register	ServerPtr	sp;

	/*** See if any Responses have completed	***/
	sp = atp->Server;
	for( i = 0; i < SQSIZE; i++ )
		if( sp[i].isInUse == WAIT_RESPONSE )
			/*** waiting for the client to acknowledge receipt ***/
			if( sp[i].pb.ATPioResult <= 0 ){
				error = sp[i].pb.ATPioResult;
				sp[i].isInUse == IDLE;
				QueueGetRequest( &(sp[i]), &(atp->ServerAddr) );
			}
}


Get_Responses_From_Servers(paramPtr, atp  )
	XCmdBlockPtr	paramPtr;
	ATPBlock		*atp;
/*********************************
* Get_Responses_From_Servers
*
* For all elements in the request queue:
* if a sndRequest completes:
*	- get the responding data and put it into the global receive containter
*	- if the request contained a response message, call hypercard back
*	  with the message.
*	- dequeue this request
*
* In: 		paramPtr	= used for callbacks
*			atp			= ATPBlock pointer
*
* Out:		calls back hypercard with the request
*			error result returned.
*********************************/
{
	/* auto */	short		i,j;
	/* auto */	OSErr		error 	= noErr;
	/* auto */	ClientPtr	cp;
	/* auto */	Handle		name, RcvData;
	register	char		*respData;
	register	short		respSize;

	cp	= atp->Client;
	for( i = 0; i < CQSIZE; i++ )
		if( cp[i].isInUse == IDLE )
			SendWaitingRequest( atp, &(cp[i]) );
		else{
			if( cp[i].pb.ATPioResult < 0 ){
				/*** next user of the block will clean up the dangling data	***/
				cp[i].isInUse = IDLE;
				error = cp[i].pb.ATPioResult;
				if( cp->rspM ){
					HUnlock( cp->rspM );
					DisposHandle( cp->rspM );
					cp->rspM = nil;
				}
			}
			if(  cp[i].pb.ATPioResult == 0 ){
				/*** got an answer from the server...							***/
				/***															***/
				/*** To calculate the size, add up the sizes of each BDS		***/
				/*** element. The buffers are contiguous so we don't have		***/
				/*** to concatentate them to create the response message		***/
				cp[i].isInUse = IDLE;
		 		respData 	= (cp[i]).bds[0].buffPtr;
				respSize	= 0;
			
				for( j = 0; j < (short)cp[i].pb.ATPnumOfResps; j++ )
					respSize += cp[i].bds[j].dataSize;
				
				/*** put the responding data in global  pool and call back		***/
				/*** hypercard with the response message 						***/			
				name = (StringPtr *)GetResource('STR ', GlobalRcvData ); 	
				RcvData = NewHandle( (long)respSize );
				
				if( RcvData ){
					BlockMove( respData, *RcvData, (long)respSize );
					MoveHHi( name );
					HLock(name);			
					SetGlobal(paramPtr, *name, RcvData ); 
					HUnlock(name);
					DisposHandle( RcvData ); /* 2/29/88 */
				}
				
				/*** If response message was given, call back hypercard with	***/
				/*** the message (handler finds its data in GlobalRcvData)		***/
				if( cp[i].rspM ){
					MoveHHi( cp[i].rspM );
					HLock( cp[i].rspM );
					c2pstr( *(cp[i].rspM) );				
					SendCardMessage( paramPtr, *(cp[i].rspM) );
					HUnlock( cp[i].rspM );
					DisposHandle( cp[i].rspM );
					cp[i].rspM = nil;
				}
			}
		}
	return( error );
}


/*******************************************************************\
*	 			(ATP) Interface Support Routines					*
*																	*
* These routines are used in support of the ATP Interface routines	*
* and are not called directly by the XCMD routines.					*
\*******************************************************************/


DeQueueRequest( atp  )
	ATPBlock		*atp;
/*********************************
* Remove an element from the waiting 
* queue and update the linked list 
* accordingly.
*
* Note: this is the only place an element can 
* 		be removed from the queue.  We use a FIFO
*		method so that tq is ALWAYS pointing to
*		the first element in the list!
*
* In: 	tq = handle to the element 
*		to dequeue
*********************************/
{
	register	ClientQH		tq = atp->FirstRequest;
	register	ClientQPtr		pq; /*** pointer for efficiency 	***/
		
	if( !tq ){
		atp->FirstRequest = atp->LastRequest = nil;
		return;						/*** No queue element here			***/
	}
	
	pq	= *tq;
											/*** next becomes first 	***/
	if( pq->next )							/*** if there is one		***/
		atp->FirstRequest = pq->next;
	else							/*** ...else the list is empty		***/
		atp->FirstRequest = atp->LastRequest = nil;

	if( pq->buf ){
		HUnlock( pq->buf );			/*** Delete the element's data		***/
		DisposHandle( pq->buf );		
	}
	
	if( pq->rsp ){
		HUnlock( pq->rsp );
		DisposHandle( pq->rsp );
	}
	
	HUnlock( tq );
	DisposHandle( tq );		
}


QueueGetRequest( sp, srcaddr )
	ServerPtr		sp;
	AddrBlock		*srcaddr;
/*********************************
* requeue the  get request element
* whose index is passed as a param.
*
* Note: This is a server side 
* function only. You may 
* have more than one getrequest
* "listening" on the server socket.
*
* if the request if not completed,
* do nothing.
*
* In:	sp		== pointer to request array element
*		srcaddr == source address of this node
*
* returns nil if the request was queued
* noErr otherwise
*********************************/
{
	/*auto */	short		error 	= DEFAULT_ERROR;
	register	atpPBptr	pb		= &(sp->pb);

	pb->ATPioCompletion	= nil;
	pb->ATPatpSocket	= srcaddr->aSocket;
	pb->ATPreqLength	= (short)ATPBSIZE;
	pb->ATPreqPointer	= sp->buf;
	sp->isInUse 		= WAIT_REQUEST;		/*** set for the sake of Kill	***/
		
	if( (error = PGetRequest( pb , ASYNC ))  >= 0 )
		error = noErr;

	return( error );
}


QueueRequest( atp, theData, theSize, rmess, addr, cnt, intv )
	ATPBlock		*atp;
	unsigned char	*theData;
	short			theSize;
	Handle			rmess;
	AddrBlock		*addr;
	short			cnt, intv;
/*********************************
* Add an element to the waiting 
* queue and update the linked list 
* accordingly.
*
* Note: this is the only place an element can 
* 		be added to the queue.
*
* In:		theData		== the data to use
*			theSize		== the size of the data
*			rmess		== handle to the message to 
*						   append the response to
*			addr		== tuple of the receiver
*			retry, timeout
*********************************/
{
	/* auto */	long		respSize;				
	/* auto */	ClientQH	qelem;
	register	ClientQPtr	qp;					/*** ptr to client queue element***/

	if( (qelem = NewHandle( sizeof( ClientQRec ))) == nil )
		return( DEFAULT_ERROR );
	
	/*** copy the pending data into the qelement ***/
	MoveHHi( qelem );
	HLock( qelem );
	qp				= *qelem;
	qp->next 		= nil;
	qp->interval 	= intv;
	qp->count 		= cnt;
	qp->addr.aNet 	= addr->aNet;
	qp->addr.aNode	= addr->aNode;
	qp->addr.aSocket= addr->aSocket;
	qp->size 		= (long)theSize;
	qp->rsp 		= nil;
	qp->buf 		= NewHandle( qp->size );
	
	if( qp->buf  )
		BlockMove( theData, *(qp->buf), qp->size );
	else{
		HUnlock( qelem );
		DisposHandle( qelem );
		return( MEM_ERROR );
	}

	if( rmess  ){		/*** if we have a handle and it's not empty		***/
		respSize = GetHandleSize( rmess );
		qp->rsp = NewHandle( respSize );
		if( qp->rsp )
			BlockMove( *rmess, *(qp->rsp), respSize );
		else{
			HUnlock( qp->buf );
			DisposHandle( qp->buf );
			HUnlock( qelem );
			DisposHandle( qelem );
			return( MEM_ERROR );
		}
	}		
	
	/*** Only after we're sure we put the request together do we add it to		***/
	/*** the queue.																***/
	if( !atp->FirstRequest )
		atp->FirstRequest = atp->LastRequest = qelem;
	else{ 										/*** add to end of list 		***/
		qp = *(atp->LastRequest);
		qp->next 			= qelem;
		atp->LastRequest 	= qelem;
	}
	HUnlock( qelem );
}



short	Respond(  sp, srcAddr, theData, theSize )
	ServerPtr		sp;
	AddrBlock		*srcAddr;
	char			*theData;
	short			theSize;
/*********************************
* Send a response to a requesting
* socket.  This is a server side
* function only.
*
* This routine reuses the parameter
* block that was used by the getrequest.
* Because of this, the destination address
* and the transaction ID will already be 
* valid.
*
* In: 		sp			== pointer to the server block to use
*			srcAddr		== this server's address
*			theData		== the data to use
*			theSize		== the size of the data
*
* Out:		noErr if successful
*			OSErr otherwise
*********************************/
{
	/* auto */	OSErr		error 	= noErr;
	/* auto	*/	short		bdsSize, i, bdsCount, bdsBufSiz;
	register	char		*bdsPtr;
	register	atpPBptr	pb 		= &(sp->pb);

	/*** Note: because we are re-using the getrequest parameter block, the		***/
	/*** transaction ID and the destination address are already set in the 		***/
	/*** parameter block.														***/
	sp->isInUse 				= WAIT_RESPONSE;
	pb->ATPioCompletion			= nil;
	pb->ATPatpSocket			= srcAddr->aSocket;
	pb->ATPatpFlags           	= atpEOMvalue;
	pb->ATPbdsPointer			= sp->rspBDS;
	
	/*** set up the response BDS for this transactioncopy the data into the 	***/
	/*** rspbuffer (set last  byte to zero )									***/
	
	bdsSize = ( theSize < (ATPBSIZE * MAXBDS) )? theSize : (ATPBSIZE * MAXBDS);
	BlockMove( theData, sp->rspBuf, (long)bdsSize );
	(sp->rspBuf)[bdsSize] = '\0';					/*** force to a "C" string 	***/

	/*** Set up response bds by pointing into the data at ATPBSIZE intervals	***/
	
	bdsPtr 		= sp->rspBuf;
	bdsCount 	= ( bdsSize + ATPBSIZE ) / (short)ATPBSIZE;	/*** # bds elements	***/
	
	for( i = 0; i < bdsCount; i++ ){
		sp->rspBDS[i].buffPtr		= bdsPtr;
		bdsBufSiz					= ( bdsSize < ATPBSIZE)? bdsSize : ATPBSIZE;
		sp->rspBDS[i].buffSize 		= bdsBufSiz;
		bdsSize -= ATPBSIZE;
		if( bdsSize < 0 )
			bdsSize = 0;
		bdsPtr		+=	ATPBSIZE;
		
		/*** fix up the BDS to make it pretty for the scope ***/
		sp->rspBDS[i].userBytes 	= (long)'BDS0';
		sp->rspBDS[i].userBytes 	+= (long)i;		
		
	}
	
	pb->ATPbdsSize			= (Byte)bdsCount;		/*** total # of bds elems	***/
	pb->ATPnumOfBuffs		= (Byte)bdsCount;		/*** number being sent now	***/
		
	PSendResponse( pb, ASYNC );				/*** will catch the error on idle	***/
	return( error );
}


SendRequest( cp, theData, theSize, rmess, addr, count, interval )
	ClientPtr		cp;
	unsigned char	*theData;
	short			theSize;
	Handle			rmess;
	AddrBlock		*addr;
	short			count, interval;
/*********************************
* Sends the request using the transaction
* block pointed to by cp. This is a client
* side function only.
*
* In:		cp		== pointer to transaction block
*			theData	== the data to use
*			theSize	== the size of the data
*			rmess	== the method to send back with the response
*			addr	== tuple of the receiver
*			retry, timeout
*
* Out:		noErr if successful
*			OSErr otherwise
*********************************/
{
	/* auto	*/	OSErr		error = noErr;
	/* auto	*/	long		rsize;
	/* auto	*/	short		i;
	/* auto	*/	char		*bdsPtr;
	register	atpPBptr	pb = &(cp->pb);
	
	cp->rspM = nil;
	
	/*** copy the message over ***/
	if( rmess ){
		rsize = GetHandleSize( rmess );
		cp->rspM = NewHandle( rsize );
		if( !cp->rspM )
			return( DEFAULT_ERROR );
		else
			BlockMove( *rmess, *(cp->rspM), rsize );
	}
	
	/*** set up the parameter block ***/
	pb->ATPioCompletion		= nil;
	pb->ATPuserData			= (long)0x52455153;	/*** REQS ***/	
	pb->ATPatpFlags    		= atpXOvalue;
	pb->ATPaddrBlock.aNet	= addr->aNet;
	pb->ATPaddrBlock.aNode	= addr->aNode;
	pb->ATPaddrBlock.aSocket= addr->aSocket;
	
	/*** Set up the request BDS 												***/
	/*** first copy the data into the local buffer								***/
	theSize	= (theSize < ATPBSIZE  )? theSize : ATPBSIZE;
	BlockMove( theData, cp->buf, (long)theSize );
	(cp->buf)[theSize] 	= '\0';			/*** request MUST BE null terminated	***/
	pb->ATPreqLength	= theSize;
	pb->ATPreqPointer	= cp->buf;
	
	/*** set up the response BDS 												***/
	bdsPtr	= cp->rsp;
	
	for( i = 0; i < MAXBDS; i++ ){
		cp->bds[i].buffPtr		= bdsPtr;
		cp->bds[i].buffSize		= ATPBSIZE;
		cp->bds[i].dataSize		= 0;
		cp->bds[i].userBytes	= 0L;
		bdsPtr					+=	ATPBSIZE;
	}

	pb->ATPbdsPointer	= cp->bds;		/*** bds points to response 		***/
	pb->ATPnumOfBuffs	= MAXBDS; 		/*** max num of response elements	***/
	
	/*** Do the timeout parameters and issue the request...					***/
	pb->ATPtimeOutVal	= interval;
	pb->ATPretryCount	= count;
	cp->isInUse			= WAIT_RESPONSE;

	error = PSendRequest( pb, ASYNC);	/*** error will be picked up later	***/

	if( error >= 0 )
		error = noErr;
	else								/*** we fell down, so deallocate	***/
		if( cp->rspM ){ 
			HUnlock( cp->rspM );
			DisposHandle( cp->rspM );
			cp->rspM = nil;
		}
	return( error );
}


SendWaitingRequest(  atp, cp )
	ATPBlock		*atp;
	ClientPtr		cp;
/*********************************
* Attempt to send a waiting request from
* the waiting queue using the transaction
* block whose index is passed.
*
* We step through the waiting queue until
* we find the next available element.  Attempt
* to send it. If it sends ok, delete it from
* the queue, otherwise, keep it around for a
* whack at it later.
*
* In: i ==	index to available client
*			transaction block
*
*********************************/
{
	/* auto */	short		error 	= noErr;	
	register	ClientQH	rq 		= atp->FirstRequest;
	register	ClientQPtr	p 		= nil;
	
	if( !rq )
		return( NO_ERROR );			/*** nobody in the queue 			***/
	
	MoveHHi( rq );
	HLock( rq );
	p = *rq;
	
	if( p->buf ){					/*** only send if we have data		***/
		MoveHHi( p->buf );
		HLock( p->buf );
		error = SendRequest(cp,*(p->buf),(short)p->size,p->rsp,&(p->addr),p->count,p->interval);	
		HUnlock( p->buf );
	}
	HUnlock( rq );
	if( !error )						
		DeQueueRequest( atp  );		/*** got through, take the request away		***/
		
}




/*******************************************************************\
*			 Name Binding Protocol (NBP) Routines					*
\*******************************************************************/

NBPBlock	*NBPInit()
/*********************************
* Initialize the NBP side for the
* network.
*
* Returns a pointer to an NBP block
* if the open was successful, nil 
* otherwise.
*
*********************************/
{
	char			*temp;
	short			i;
	NBPBlock		*nbp = nil;
	
	/*** attempt to allocate the data 				***/
	if( !(nbp	= (Ptr)NewPtr( sizeof( NBPBlock ))) )
		return( nil );

	/*** 			------	Initialize the NBP side	------				***/
	nbp->NTEntry.nt.nteAddress.aSocket	= 0;
	nbp->NTEntry.nt.nteAddress.aNode	= 0;
	nbp->NTEntry.nt.nteAddress.aNet		= 0;
	
	temp = nbp->NTEntry.nt.entityData;
	for( i = 0; i < 99; i++ )		/*** zero out the name string		***/
		*temp++ = '\0';	
		
	nbp->LookUpBuffer	= nil;
	nbp->EntCount		= 0;
	nbp->Registered		= false;
	
	return( nbp );
}


short	NBPKill( nbp )
	NBPBlock	*nbp;
/*********************************
* Disassociate this node with the network
* and release all memory used by NBP
*
* No nbp calls are made asynchronously
* so no need to shut any of them down.
*********************************/
{
	/* auto */	OSErr			error = noErr;
	/* auto	*/	char			*temp;
	/* auto */  MPPParamBlock	pb;
	
	/*** 	Close Down and deallocate  NBP side			***/
	temp = 	nbp->NTEntry.nt.entityData;	
	if( *temp ){
		pb.NBPentityPtr = nbp->NTEntry.nt.entityData;
		error = PRemoveName( &pb, SYNC );
	}
	
	if( nbp->LookUpBuffer ){
		HUnlock( nbp->LookUpBuffer );
		DisposHandle( nbp->LookUpBuffer );
	}
	
	DisposPtr( nbp );
	return( error );
}


short	ConfirmName(  nbp, name, addr, count, interval )
	NBPBlock		*nbp;
	EntityName		*name;
	AddrBlock		*addr;	
	short			count, interval;
/*********************************
* Confirm that the name of a network
* visible  entity is still visible
* to us.
*
* In: 		name = pointer to name
*			in the form of a names table entry.
*			addr = pointer to address
*
* Out:		true 	if successful, socket set in addr
*			false 	otherwise
*********************************/
{
	/*auto */	OSErr			error 	= noErr;
	register	MPPParamBlock	Mpb;
	
	Mpb.MPPioCompletion 		=	nil;		/*** as a matter of form 		***/
	
	Mpb.NBPentityPtr			= name;
	Mpb.NBPconfirmAddr.aNet		= addr->aNet;
	Mpb.NBPconfirmAddr.aNode	= addr->aNode;
	Mpb.NBPconfirmAddr.aSocket	= addr->aSocket;
	Mpb.NBPnewSocket			= 0;
	Mpb.NBPcount		 		= count;
	Mpb.NBPinterval				= interval;
	
	if( (error = PConfirmName( &Mpb, SYNC )) == noErr )
		addr->aSocket = Mpb.NBPmaxToGet;
	
	if( error  )
		return( false );
	else
		return( true );
}



short ExtractName(  nbp, who, name, addr )
	NBPBlock		*nbp;
	short			who;
	EntityName		*name;
	AddrBlock		*addr;
/*********************************
* Extract a name from the socket
* listening table by index.  Sets the
* send entity to this entity.
*
* In: 		who			== Index to name to extract
*			name  		== pointer to name result 
*			addr		== pointer to addr result
* Out:		1 	if successful
*			0 	if not successful
*			-1  if all entities searched
*
* Note that there is no "preferred" call
* for extract name.  Because NBPExtract 
* doesn't need a parameter block, no preferred
* call was needed.
*********************************/
{
	/* auto */	short			error;
	/* auto */	short			found = 0;
	
	if( (nbp->EntCount <= 0) || (who > nbp->EntCount) )
		return(  -1  );			/*** no network visible entities 		***/
	
	MoveHHi( nbp->LookUpBuffer );
	HLock( nbp->LookUpBuffer );
	if( !(NBPExtract( *(nbp->LookUpBuffer),nbp->EntCount,who,name,addr)) )
		found = 1;
		
	HUnlock( nbp->LookUpBuffer );
	
	return( found );
}


short	Lookup(  nbp, theType, theZone, num, count, interval )
	NBPBlock		*nbp;
	char			*theType;
	char			*theZone;
	short			num, count, interval;
/*********************************
* Lookup all network visible entities
* and fill the Lookup buffer accordingly
*
* In: 		(Handle) NBPBlock
*			theType = type to lookup
*			theZone = zone to lookup
*			num		= number to lookup
*			count 	= (number of retries)
*			interval= (X8 ticks)time between retries
*
* Out:		number of elements returned
*********************************/
{
	/* auto */	short			error;
	/* auto */	short			i;
	/* auto */	NamesTableEntry	nte;		/*** used internally by PLookupName	***/
	/* auto */	long			EntSize;
	/* auto */	MPPParamBlock	Mpb;
	register	char			*np;		/*** to fill out the names table	***/
	
	/*** resize the record ***/
	if( nbp->LookUpBuffer ){
		HUnlock( nbp->LookUpBuffer );
		DisposHandle( nbp->LookUpBuffer );
	}
	
	nbp->EntCount 		= 0;
	EntSize 			= ( sizeof( NamesTableEntry) );	
	nbp->LookUpBuffer 	= NewHandle( (long)(num * EntSize) );
	
	if( nbp->LookUpBuffer ){
		MoveHHi( nbp->LookUpBuffer );
		HLock( nbp->LookUpBuffer );
	}
	else
		return( DEFAULT_ERROR );	/*** no room left for lookup buffer  	***/
	
	/***	Set the Search string to lookup everyone of requested type 		***/
	/***	the search string gets put away in a names table entry for 		***/
	/*** 	the preferred AppleTalk call									***/
	/*** 	-----------------------------------------------------------		***/
	/***	The use of a names table entry as opposed to an EntityName is	***/
	/***	a matter of form.  Lookup wants to see the name in tuple form	***/
	/***	(i.e. the entityname is packed).  We borrow the entityData from	***/
	/*** 	the namestableentry element (nte) and ignore the rest of the 	***/
	/***	structure.  The use of a string[99] would have the same effect.	***/
	
	nte.qNext					= nil;
	nte.nt.nteAddress.aNet 		= 0;
	nte.nt.nteAddress.aNode		= 0;
	nte.nt.nteAddress.aSocket	= 0;
	
	np 		= nte.nt.entityData;
	*np++ 	= '\1';							
	*np++ 	= '=';					/*** all entities of this type please	***/
	BlockMove( theType, np, (long)(*theType+1));
	np += (*theType)+1;
   	BlockMove( theZone, np, (long)(*theZone+1) );	

	Mpb.NBPntQElPtr		= nte.nt.entityData;
	Mpb.NBPretBuffPtr 	= *(nbp->LookUpBuffer);
	Mpb.NBPretBuffSize 	= (short)(EntSize * num);
	Mpb.NBPmaxToGet		= num;
	Mpb.NBPinterval		= interval;
	Mpb.NBPcount		= count;
	
	if( !(error = PLookupName( &Mpb, SYNC)) )
		nbp->EntCount 	= Mpb.NBPnumGotten;

	HUnlock( nbp->LookUpBuffer );
	return( nbp->EntCount );
}


short	NodeRegister(  nbp, nameOf, typeOf, count, interval, verify, addr )
	NBPBlock		*nbp;
	char			*typeOf;
	char			*nameOf;
	short			count;
	short			interval;
	short			verify;
	AddrBlock		*addr;
/*********************************
* Register this node as the entity
* passed as a parameter.
*
* In: 		(Ptr) 	typeof entity (provided by hyperTalk)
*			(Ptr) 	nameof entity (from system resource) 
*			short	retry interval
*			short 	retry count
*			short	verify = name must be unique.
* Out:		noErr if successful
*			OSErr otherwise
*********************************/
{
	/* auto */	short			error;
	register	NamesTableEntry	*nte = &(nbp->NTEntry);
	register	char			*np;		/*** pointer into names table	***/
	/* auto */	MPPParamBlock	Mpb;

	/*** save off the entity in a packed names array 	***/
	np 	= nte->nt.entityData;
	BlockMove( nameOf, np, (long)(*nameOf)+1 );
	np += (*nameOf)+1;
	BlockMove( typeOf, np, (long)(*typeOf)+1 );
	np += (*typeOf)+1;
	
	/*** According to Insdide AppleTalk, Zone Name must be '*' (this zone) ***/
	*np++ = '\1';
	*np++ = '*';
	
	nte->nt.nteAddress.aSocket = addr->aSocket;
	nte->nt.nteAddress.aNode = addr->aNode;
	nte->nt.nteAddress.aNet = addr->aNet;

	Mpb.NBPinterval		= interval;
	Mpb.NBPcount		= count;
	Mpb.NBPntQElPtr		= nte;
	Mpb.NBPverifyFlag	= verify;
	
	error = PRegisterName( &Mpb, SYNC);
	
	if( !error )
		nbp->Registered = true;
		
	return( error );
}

